/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright (c) 2016 The Khronos Group Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief X11 using XCB utilities.
 *//*--------------------------------------------------------------------*/

#include "tcuLnxX11Xcb.hpp"
#include "deMemory.h"

namespace tcu
{
namespace lnx
{
namespace x11
{

XcbDisplay::DisplayState XcbDisplay::s_displayState = XcbDisplay::DISPLAY_STATE_UNKNOWN;

bool XcbDisplay::hasDisplay (const char* name)
{
	if (s_displayState == DISPLAY_STATE_UNKNOWN)
	{
		xcb_connection_t *connection = xcb_connect(name, NULL);
		if (connection && !xcb_connection_has_error(connection) )
		{
			s_displayState = DISPLAY_STATE_AVAILABLE;
			xcb_disconnect(connection);
		}
		else
		{
			s_displayState = DISPLAY_STATE_UNAVAILABLE;
		}
	}
	return s_displayState == DISPLAY_STATE_AVAILABLE ? true : false;
}

XcbDisplay::XcbDisplay (EventState& platform, const char* name)
	: DisplayBase	(platform)
{
	m_connection						= xcb_connect(name, NULL);
	const xcb_setup_t		*setup		= xcb_get_setup(m_connection);
	xcb_screen_iterator_t	iterator	= xcb_setup_roots_iterator(setup);
	m_screen							= iterator.data;
}

XcbDisplay::~XcbDisplay (void)
{
	xcb_disconnect (m_connection);
}

void XcbDisplay::processEvents (void)
{
	xcb_generic_event_t *ev;
	while ((ev = xcb_poll_for_event(m_connection)))
	{
		deFree(ev);
		/* Manage your event */
	}
}

XcbWindow::XcbWindow (XcbDisplay& display, int width, int height, xcb_visualid_t* visual)
	: WindowBase	()
	, m_display		(display)
{
	xcb_connection_t*	connection = m_display.getConnection();
	uint32_t			values[2];
	m_window	= xcb_generate_id(connection);
	m_colormap	= xcb_generate_id(connection);

	if (visual == DE_NULL)
		visual = &m_display.getScreen()->root_visual;

	values[0] = m_display.getScreen()->white_pixel;
	values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_PROPERTY_CHANGE;

	xcb_create_window	(
							connection,								// Connection
							XCB_COPY_FROM_PARENT,					// depth (same as root)
							m_window,								// window Id
							display.getScreen()->root,				// parent window
							0, 0,									// x, y
							static_cast<uint16_t >(width),			// width
							static_cast<uint16_t >(height),			// height
							10,										// border_width
							XCB_WINDOW_CLASS_INPUT_OUTPUT,			// class
							*visual,								// visual
							XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,	// masks
							values									//not used yet
						);

	xcb_create_colormap	(
							connection,
							XCB_COLORMAP_ALLOC_NONE,
							m_colormap,
							m_window,
							*visual
						);

	xcb_alloc_color_reply_t* rep = xcb_alloc_color_reply(connection, xcb_alloc_color(connection, m_colormap, 65535, 0, 0), NULL);
	deFree(rep);
	xcb_flush (connection);
}

XcbWindow::~XcbWindow (void)
{
	xcb_flush (m_display.getConnection());
	xcb_free_colormap(m_display.getConnection(), m_colormap);
	xcb_destroy_window(m_display.getConnection(), m_window);
}

void XcbWindow::setVisibility (bool visible)
{
	if (visible == m_visible)
		return;

	if (visible)
		 xcb_map_window(m_display.getConnection(), m_window);
	else
		xcb_unmap_window(m_display.getConnection(), m_window);

	m_visible = visible;
	xcb_flush (m_display.getConnection());

}

void XcbWindow::processEvents (void)
{
	// A bit of a hack, since we don't really handle all the events.
	m_display.processEvents();
}

void XcbWindow::getDimensions (int* width, int* height) const
{
	xcb_get_geometry_reply_t *geom;
	geom = xcb_get_geometry_reply(m_display.getConnection(), xcb_get_geometry(m_display.getConnection(), m_window), NULL);
	*height = static_cast<int>(geom->height);
	*width = static_cast<int>(geom->width);
	deFree(geom);
}

void XcbWindow::setDimensions (int width, int height)
{
	const uint32_t		values[]	= {static_cast<uint32_t >(width), static_cast<uint32_t >(height)};
	xcb_void_cookie_t	result;
	xcb_connection_t*	display		= m_display.getConnection();
	result = xcb_configure_window(display, m_window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
	DE_ASSERT(DE_NULL == xcb_request_check(display,result));
	xcb_flush (display);

	for(;;)
	{
		xcb_generic_event_t*	event = xcb_poll_for_event(display);
		int						w, h;
		if(event != DE_NULL)
		{
			if (XCB_PROPERTY_NOTIFY == (event->response_type & ~0x80))
			{
				const xcb_property_notify_event_t* pnEvent = (xcb_property_notify_event_t*)event;
				if (pnEvent->atom == XCB_ATOM_RESOLUTION)
				{
					deFree(event);
					break;
				}
			}
			deFree(event);
		}
		getDimensions (&w,&h);
		if (h==height || w==width)
			break;
	}
}

} // xcb
} // lnx
} // tcu
